本节代码对应 GitHub 分支: chapter4
# 上拉 / 下拉加载更多实现
在这里 Scroll 基础组件的作用就展现出来了。之前我们封装了 Scroll 组件,监听上拉 / 下拉刷新的功能已编写完成,但是相应的 loading 效果并没有考虑。现在,我们就来先完善 loading 效果。
首先引入 loading 组件:
import Loading from '../loading/index';
将 return 部分的代码修改为:
const PullUpdisplayStyle = pullUpLoading ? {display: ""} : { display:"none" };
const PullDowndisplayStyle = pullDownLoading ? { display: ""} : { display:"none" };
return (
<ScrollContainer ref={scrollContaninerRef}>
{props.children}
{/* 滑到底部加载动画 */}
<PullUpLoading style={ PullUpdisplayStyle }><Loading></Loading></PullUpLoading>
{/* 顶部下拉刷新动画 */}
<PullDownLoading style={ PullDowndisplayStyle }><LoadingV2></LoadingV2></PullDownLoading>
</ScrollContainer>
);
注意 PullUpdisplayStyle 和 PullDowndisplayStyle 都是外部传入的,这就方便了我们控制 loading 的显示和隐藏。
其中 Loading 组件即之前编写的两圆交错的涟漪效果组件,但 LoadingV2 并没有编写,现在就花一点时间来开发第二个 Loading 效果。
//baseUI/loading-v2/index.js
import React from 'react';
import styled, {keyframes} from'styled-components';
import style from '../../assets/global-style'
const dance = keyframes`
0%, 40%, 100%{
transform: scaleY (0.4);
transform-origin: center 100%;
}
20%{
transform: scaleY (1);
}
`
const Loading = styled.div`
height: 10px;
width: 100%;
margin: auto;
text-align: center;
font-size: 10px;
>div {
display: inline-block;
background-color: ${style ["theme-color"]};
height: 100%;
width: 1px;
margin-right:2px;
animation: ${dance} 1s infinite;
}
>div:nth-child (2) {
animation-delay: -0.4s;
}
>div:nth-child (3) {
animation-delay: -0.6s;
}
>div:nth-child (4) {
animation-delay: -0.5s;
}
>div:nth-child (5) {
animation-delay: -0.2s;
}
`
function LoadingV2 () {
return (
<Loading>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<span > 拼命加载中...</span>
</Loading>
);
}
export default React.memo (LoadingV2);
OK, 现在在 scroll 组件中引入。
//scroll/index.js
import Loading2 from '../loading-v2/index';
接下来,我们在 Singers/index.js 中,传入相应的参数即可。
<Scroll
pullUp={ handlePullUp }
pullDown = { handlePullDown }
pullUpLoading = { pullUpLoading }
pullDownLoading = { pullDownLoading }
>
{ renderSingerList () }
</Scroll>
现在 handlePullUp 和 handlePullDown 两个方法还没有定义,添加如下:
const handlePullUp = () => {
pullUpRefreshDispatch (category, alpha, category === '', pageCount);
};
const handlePullDown = () => {
pullDownRefreshDispatch (category, alpha);
};
现在试一试上拉下拉,相应的 loading 动画能够出现了,同时数据也能正常加载。 对了,现在进场的 loading 效果还没有实现,我们在 Singers/index.js 中加入:
import Loading from '../../baseUI/loading';
//...
//ListContainer 标签中
<Loading show={enterLoading}></Loading>
这样,当你第一次打开列表页或者切换不同分类的时候,会有 loading 效果出现,和我们的预期一致。
# 相关优化
# 图片懒加载
同样是引入 react-lazyload, 在 Singers/index.js 作如下修改:
// 首先引入
import LazyLoad, {forceCheck} from 'react-lazyload';
// 包裹 img 标签
<LazyLoad placeholder={<img width="100%" height="100%" src={require ('./singer.png')} alt="music"/>}>
<img src={`${item.picUrl}?param=300x300`} width="100%" height="100%" alt="music"/>
</LazyLoad>
<Scroll
//...
onScroll={forceCheck}
>
</Scroll>
现在懒加载的效果就完成了。
# 防抖处理
当你频繁地下拉时,事实上事件回调函数也会被频繁触发,导致发送很多无意义的请求。因此这里对 Scroll 基础组件做一下防抖处理。
防抖函数写在 api/utils.js 中,
// 防抖函数
export const debounce = (func, delay) => {
let timer;
return function (...args) {
if (timer) {
clearTimeout (timer);
}
timer = setTimeout (() => {
func.apply (this, args);
clearTimeout (timer);
}, delay);
}
}
然后在 scroll/index.js 中:
import { debounce } from "../../api/utils";
//...
let pullUpDebounce = useMemo (() => {
return debounce (pullUp, 300)
}, [pullUp]);
// 千万注意,这里不能省略依赖,
// 不然拿到的始终是第一次 pullUp 函数的引用,相应的闭包作用域变量都是第一次的,产生闭包陷阱。下同。
let pullDownDebounce = useMemo (() => {
return debounce (pullDown, 300)
}, [pullDown]);
//...
// 之后直接调用 useMemo 返回的函数
// 滑动到底部
useEffect(() => {
if(!bScroll || !pullUp) return;
const handlePullUp = () => {
//判断是否滑动到了底部
if(bScroll.y <= bScroll.maxScrollY + 100){
pullUpDebounce();
}
};
bScroll.on('scrollEnd', handlePullUp);
// 解绑
return () => {
bScroll.off('scrollEnd', handlePullUp);
}
}, [pullUp, pullUpDebounce, bScroll]);
// 判断用户的下拉动作
useEffect(() => {
if(!bScroll || !pullDown) return;
const handlePullDown = (pos) => {
//判断用户的下拉动作
if(pos.y > 50) {
pullDownDebounce();
}
};
bScroll.on('touchEnd', handlePullDown);
return () => {
bScroll.off('touchEnd', handlePullDown);
}
}, [pullDown, pullDownDebounce, bScroll]);
这样当你频繁上拉下拉的时候就不会频繁触发回调了。
思考题:当我们切换组件的时候,事实上现在的 category 和 alpha 会丢失,如果想要切换组件后仍然能够缓存 category 和 alpha 的值应该怎么做?可以自己动手试试看